package ngames.com.sushitv.net.okhttp;

import android.content.Context;
import android.util.Log;

import org.json.JSONObject;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import ngames.com.sushitv.core.ErrorEvent;
import ngames.com.sushitv.net.okhttp.callback.RequestCallBack;
import ngames.com.sushitv.net.okhttp.cookie.SimpleCookieJar;
import ngames.com.sushitv.net.okhttp.request.CountingRequestBody;
import ngames.com.sushitv.tools.LogUtil;
import ngames.com.sushitv.tools.NetUtil;
import ngames.com.sushitv.tools.Utils;
import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Created by Administrator on 2016/1/19.
 */
public class OKHttpManage {
    private static final int REQUEST_TYPE_POST = 0;
    private static final int REQUEST_TYPE_PUT = 1;
    private static final int REQUEST_TYPE_DELETE = 2;

    private static final int DIS_CACHE_SIZE = 10 * 1024 *1024;
    private static final String DIS_CACHE_FILE = "data";
    private static OKHttpManage mInstance;
    private Context mContext;
    private OkHttpClient mOkHttpClient;

    private OKHttpManage(Context context) {
        mContext = context;
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        //cookie enabled
        okHttpClientBuilder.cookieJar(new SimpleCookieJar());
        okHttpClientBuilder.cache(new Cache(Utils.getOwnCacheDirectory(context, DIS_CACHE_FILE), DIS_CACHE_SIZE));

        mOkHttpClient = okHttpClientBuilder.build();
    }

    public static OKHttpManage getInstance(Context context) {
        if (mInstance == null) {
            synchronized (OKHttpManage.class) {
                if (mInstance == null) {
                    mInstance = new OKHttpManage(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 异步get请求
     * 下载文件时用{@link ngames.com.sushitv.net.okhttp.callback.RequestCallBackDownload}
     * @param url
     * @param params
     * @param requestCallBack
     */
    public void asynRequestGet(String url, HashMap<String, String> params, RequestCallBack requestCallBack){
        String requestUrl = appendParams(mContext, url, params);
        LogUtil.d("OKHttpManage", "asynRequestGet() requestUrl:" + requestUrl);
        Request.Builder requestBuilder = new Request.Builder()
                .url(requestUrl)
                .tag(url);
        onRequest(url, requestBuilder, requestCallBack);
    }

    private static String appendParams(Context context, String url, HashMap<String, String> params) {
        String appendUrl;
        if (params != null && !params.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            sb.append(url);
            for (String key : params.keySet()) {
                sb.append(key).append("=").append(params.get(key)).append("&");
            }
//            sb = sb.deleteCharAt(sb.length() - 1);
            appendUrl = sb.toString();
        } else {
            appendUrl = url;
        }
        appendUrl = NetUtil.addCommonParameters(context, appendUrl);
        return appendUrl;
    }

    public void asynRequestJSONGet(String url, ArrayList<String> params, RequestCallBack requestCallBack) {
        asynRequestJSONGet(url, params, requestCallBack, true);
    }

    /**
     * json的get请求
     * @param url
     * @param params
     * @param requestCallBack
     * @param withCache 当网络请求错误时，是否使用缓存
     */
    public void asynRequestJSONGet(String url, ArrayList<String> params, RequestCallBack requestCallBack, boolean withCache) {
        String requestUrl = appendParamsValue(url, params);
        LogUtil.d("OKHttpManage", "requestUrl:" + requestUrl);
        cancelTag(requestUrl);
        Request.Builder requestBuilder = new Request.Builder()
                .url(requestUrl)
                .tag(url);
        onRequest(url, requestBuilder, requestCallBack, withCache);
        Log.e("httpppppppppp", url);
    }

    private static String appendParamsValue(String url, ArrayList<String> params) {
        if (params == null || params.isEmpty()) {
            return url;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(url);
        for(int i = 0; i < params.size(); i++) {
            sb.append(params.get(i)).append("/");
        }
        sb = sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    /**
     * post请求(需在请求地址拼接参数)
     */
    public void asynRequestJSONStrPost(String url, ArrayList<String> params, RequestCallBack requestCallBack) {
        String requestUrl = appendParamsValue(url, params);
        asynRequestJSONPost(requestUrl, null, requestCallBack);
    }

    /**
     * put请求(需在请求地址拼接参数)
     */
    public void asynRequestJSONStrPut(String url, ArrayList<String> params, RequestCallBack requestCallBack) {
        String requestUrl = appendParamsValue(url, params);
        asynRequestJSONPut(requestUrl, null, requestCallBack);
    }

    /**
     * delete请求(需在请求地址拼接参数)
     */
    public void asynRequestJSONStrDelete(String url, ArrayList<String> params, RequestCallBack requestCallBack) {
        String requestUrl = appendParamsValue(url, params);
        asynRequestJSONDelete(requestUrl, null, requestCallBack);
    }

    /**
     * 异步JSON POST请求
     * @param url
     * @param params
     * @param requestCallBack
     */
    public void asynRequestJSONPost(String url, JSONObject params, RequestCallBack requestCallBack) {
        asynRequestJSON(REQUEST_TYPE_POST, url, params, requestCallBack);
    }

    /**
     * put请求
     */
    public void asynRequestJSONPut(String url, JSONObject params, RequestCallBack requestCallBack) {
        asynRequestJSON(REQUEST_TYPE_PUT, url, params, requestCallBack);
    }

    /**
     * delete请求
     */
    public void asynRequestJSONDelete(String url, JSONObject params, RequestCallBack requestCallBack) {
        asynRequestJSON(REQUEST_TYPE_DELETE, url, params, requestCallBack);
    }

    public void asynRequestJSON(int requestType, String url, JSONObject params, RequestCallBack requestCallBack) {
        LogUtil.d("OKHttpManage", "asynRequestJSON() url = " + url + ", requestType = " + requestType);
        RequestBody body = getJSONRequestBody(params);
        Request.Builder requestBuilder = new Request.Builder()
                .url(url)
                .tag(url);
        switch (requestType) {
            case REQUEST_TYPE_POST:
                requestBuilder.post(body);
                break;
            case REQUEST_TYPE_PUT:
                requestBuilder.put(body);
                break;
            case REQUEST_TYPE_DELETE:
                requestBuilder.delete(body);
                break;

            default:
                break;
        }
        onRequest(url, requestBuilder, requestCallBack);
    }

    private static RequestBody getJSONRequestBody(JSONObject params) {
        MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        String content = params == null ? "" : params.toString();
        LogUtil.d("OKHttpManage", "Request josn = " + content);
        return RequestBody.create(JSON, content);
    }

    /**
     *  异步文件上传(需要在请求地址上拼接参数)
     * @param url
     * @param params
     * @param files
     * @param fileKeys
     * @param requestCallBack
     */
    public void asynUpLoadStrPost(String url, ArrayList<String> params, File[] files, String[] fileKeys, RequestCallBack requestCallBack) {
        String requestUrl = appendParamsValue(url, params);
        asynUpLoadPost(requestUrl, null, files, fileKeys, requestCallBack);
    }

    /**
     *  异步文件上传
     * @param url
     * @param params
     * @param files
     * @param fileKeys
     * @param requestCallBack
     */
    public void asynUpLoadPost(String url, HashMap<String, String> params, File[] files, String[] fileKeys, RequestCallBack requestCallBack) {
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        addParams(builder, params);
        if (files != null) {
            RequestBody fileBody = null;
            File file = null;
            for (int i = 0; i < files.length; i++) {
                file = files[i];
                if (file == null) {
                    continue;
                }
                String fileName = file.getName();
                fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
                //TODO 根据文件名设置contentType
//                builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + fileKeys[i] + "\"; filename=\"" + fileName + "\""), fileBody);
                builder.addFormDataPart(fileKeys[i], fileName, fileBody);
            }
        }
        RequestBody requestBody = wrapRequestBody(builder.build(), requestCallBack);

        Request.Builder requestBuilder =  new Request.Builder()
                .url(url)
                .post(requestBody)
                .tag(url);
        onRequest(url, requestBuilder, requestCallBack);
    }

    private  static void addParams(MultipartBody.Builder builder, HashMap<String, String> params) {
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""), RequestBody.create(null, params.get(key)));
            }
        }
    }

    private static String guessMimeType(String path) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }

    private static RequestBody wrapRequestBody(RequestBody requestBody, final RequestCallBack callback) {
        if (callback == null) return requestBody;
        CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody, new CountingRequestBody.Listener() {
            @Override
            public void onRequestProgress(final long bytesWritten, final long contentLength) {
                callback.progress((int) (bytesWritten / contentLength * 100));
            }
        });
        return countingRequestBody;
    }

    private void onRequest(String urlTag, Request.Builder requestBuilder, final RequestCallBack requestCallBack){
        onRequest(urlTag, requestBuilder, requestCallBack, false);
    }
    private void onRequest(final String urlTag, Request.Builder requestBuilder, final RequestCallBack requestCallBack, final boolean withCache){
        final Request request = requestBuilder.build();
        final Call c = mOkHttpClient.newCall(request);
        requestCallBack.onBefore(c, urlTag);
        c.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtil.d("OKHttpManage", "onFailure():" + e.toString() + ", url = " + urlTag);
                if (withCache) requestFromCache(mOkHttpClient, request, requestCallBack);
                String code = Utils.isNetConnect(mContext) ? ErrorEvent.CODE_ERROR_REQUEST : ErrorEvent.CODE_ERROR_NO_INTERNET;
                requestCallBack.failure(code, e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) {
                LogUtil.d("OKHttpManage", "onRequest() " + response.code() + ", url = " + urlTag);
                if (response.code() == 200) {
                    requestCallBack.parseResponse(response);
                } else {
                    if (withCache) {
                        requestFromCache(mOkHttpClient, request, requestCallBack);
                    }
                    try {
                        requestCallBack.failure(ErrorEvent.CODE_ERROR_REQUEST, response.body().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    private static void requestFromCache(OkHttpClient okHttpClient, Request request, final RequestCallBack requestCallBack) {
//        mOkHttpClient.cache().get(request);
        Cache cache = okHttpClient.cache();
        try {
            Method m = cache.getClass().getDeclaredMethod("get", Request.class);
            m.setAccessible(true);
            Response response = (Response) m.invoke(cache, request);
            if (response == null) {
                requestCallBack.failure(ErrorEvent.CODE_ERROR_REQUEST, "");
            } else if (response.code() == 200) {
                requestCallBack.parseResponse(response);
            } else {
                requestCallBack.failure(ErrorEvent.CODE_ERROR_REQUEST, response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public void cancelTag(Object tag) {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }
    public void asynFormPost(String url, HashMap<String, String> params, RequestCallBack requestCallBack) {
        RequestBody requestBody = getFormRequestBody(mContext, params);
        Request.Builder requestBuilder = new Request.Builder()
                .url(url)
                .tag(url)
                .post(requestBody);
        onRequest(url, requestBuilder, requestCallBack);
    }
    private static RequestBody getFormRequestBody(Context context, HashMap<String, String> params) {
        FormBody.Builder formBody = new FormBody.Builder();
        params = NetUtil.addCommonParameters(context, params);
        for (Map.Entry<String, String> entry : params.entrySet()) {
            formBody.add(entry.getKey(), entry.getValue());
            LogUtil.d("OKHttpManage", "getFormRequestBody() " + entry.getKey() + " = " + entry.getValue());
        }
        RequestBody requestBody = formBody.build();
        return requestBody;
    }


}
